<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>excel-replay</title>
  <style>
  table{
    border-collapse:collapse;
  }
  table,th,td{
    border: 1px solid black;
  }
  </style>
</head>
<body>
  <div id="app"></div>

  <script type="text/javascript" src="../js/react.js"></script>
  <script type="text/javascript" src="../js/react-dom.js"></script>
  <script type="text/javascript">
    var header = ["Book","Author","Language"];
    var exceldata = [["Think in java","janmes","JAVA"],["C++ Permier","zhang san","C++"],["python编程思想","wang wu","python"]];

    var excel = React.createClass({
      displayName: 'excel',
      _log:[],

      /**
       * 另外一种实现方式：
       * 这种方式修改了this.setState()方法的调用，有点不太友好，还可以通过生命周期做。
       * 通过生命周期做的方法没实验，后期有时间可以试一下，大致如下：
       * 在componentWillMount方法中，对初始化的state做_log的第0个加入
       * 在componentDidUpdate(oldPros, oldState),在组件更新完成后，仍然可以获得之前的props和state，这时可以在把oldstate，push到_log里
       * @param  {[type]} newState [description]
       * @return {[type]}          [description]
       */
      _logState: function(newState) {
        if(this._log.length === 0) {
          this._log.push(JSON.parse(JSON.stringify(this.state)));
        };
        this._log.push(JSON.parse(JSON.stringify(newState)));
        // this._log.push(JSON.parse(JSON.stringify(
        //   this._log.length === 0 ? this.state : newState
        // )));
        this.setState(newState);
      },

      componentDidMount: function() {
        document.onkeydown = function(e) {
          if(e.altKey && e.shiftKey && e.keyCode === 82) {//alt + shift + R(replay)
            this._replay();
          }
        }.bind(this);
      },
      _replay: function() {
        if(this._log.length === 0) {
          console.warn('No state to replay yet');
          return;
        }

        var idx = -1;
        var interval = setInterval(function(){
          idx++;
          if(idx === this._log.length - 1) {
            clearInterval(interval);
          }
          this.setState(this._log[idx]);
        }.bind(this), 1000);
      },

      propTypes: {
        header: React.PropTypes.array,
      },
      getInitialState: function() {
        return {
          header: this.props.header,
          data: this.props.initdata,
          sortby: null,
          descending: false,//降序默认是false，也就是默认升序排序
          search: false,
        };
      },

      _sort:function(ev) {
        var column = ev.target.cellIndex;
        // this.state.sortby = column;  //错误的赋值方式，如果要设置state里的sortby，需要通过setState()方法设置

        // 只有当排序字段是同一字段，且，当前排序方式不是降序的时候，才进行降序排序
        var isDesc = this.state.sortby === column && !this.state.descending

        // slice()是数组的深度拷贝
        var sortdata = this.state.data.slice();
        // 数组的sort()方法，默认是按照ASIIC的先后循序排序，如果要按其他的方式排序，需要传入一个函数作为参数，函数传输两个参数，返回两个参数比较的大小
        sortdata.sort(function(a, b) {
          return isDesc
           ? (a[column] < b[column] ? 1 : -1)//降序排列
           : (a[column] < b[column] ? -1 : 1)//升序排列
        });

        this._logState({
          data: sortdata,
          sortby: column,//这样对state的赋值方式才正确
          descending: isDesc,
          editIdex: null,//{row: rowIdex, cell: cellIndex}
        });
      },

      _showEdit: function(ev) {
        this._logState({
          editIdex: {
            row: parseInt(ev.target.dataset.row, 10),
            cell: ev.target.cellIndex,
          }
        });
      },

      _save: function(ev) {
        ev.preventDefault();
        var input = ev.target.firstChild;
        var data = this.state.data.slice();

        data[this.state.editIdex.row][this.state.editIdex.cell] = input.value;

        this._logState({
          editIdex: null,
          data: data,
        })
      },
      // 渲染table组件
      _renderTable: function() {
        return React.DOM.table(null,
          // thead
          React.DOM.thead(null,
            this._renderSearch(),//这里会判断search开关，打开时渲染search搜索框
            React.DOM.tr({onClick: this._sort},
              this.state.header.map(function(title, idx){
                var sortby = this.state.sortby;
                if( sortby === idx) {
                  title = title + (this.state.descending ? ' \u2193': ' \u2191');
                }
                return React.DOM.th({key: idx}, title);
              }, this)//注意，这里要传入this，否则回调函数里，是不知道外边对象的，也就是回调函数里的this是undefined
            )
          ),
          // tbody
          React.DOM.tbody({onDoubleClick: this._showEdit},//_showEdit函数的意义就在于，根据事件源定义双击单元格的坐标，然后重新render整个table，render table时下边就根据定位坐标渲染文本框
            this.state.data.map(function(row, rowid){
              return React.DOM.tr({key: rowid},
                row.map(function(cell, idx) {
                  var content = cell;
                  var editflag = this.state.editIdex;
                  if(editflag && rowid === editflag.row && idx === editflag.cell) {
                    content = React.DOM.form({onSubmit: this._save},  //将react组件赋值给 context，这样td在渲染的时候，可以渲染文字，可以渲染子组件
                      React.DOM.input({type: 'text', defaultValue: content})
                    );
                  }

                  return React.DOM.td(
                    {
                      key: idx,
                      'data-row': rowid
                    },
                    content);//这个context也许是文字，也许是组件
                }, this)//这个this，是下边传进来的this对象
              );
            }, this)
          )
        )
      },

      // 这个组件仅渲染一个button 和一个onclick事件
      _renderToolbar: function() {
        return React.DOM.button(
          {
            onClick: this._toggleSearch,
            className: 'toolbar'
          },
          // 'search'
          this._changeText(),
        )
      },

      _changeText: function() {
        if(this.state.search) {
          return "关闭";
        } else {
          return "打开";
        }
      },

      // 定义一个_preSearchData对象，存储原来的表格数据，防止表格过滤后不能恢复到原来的数据
      _preSearchData: null,

      // 切换开关，主要是切换 search 开关，是否显示搜索框。调用setState，重新渲染render方法，也就调用了rendertable。
      _toggleSearch: function() {
        if(this.state.search) {
          this._logState({
            data: this._preSearchData,
            search: false,
          })
          this._preSearchData = null;
        } else {
          this._preSearchData = this.state.data;
          this._logState({
            search: true,
          });
        }
      },

      // 调用rendertable时，就会调用这里，如果search开关打开，就渲染一行搜索文本框
      _renderSearch: function() {
        if(!this.state.search) {
          return null;
        }

        return React.DOM.tr({onChange: this._search},//定义一个文本框的onchange事件
          this.props.header.map(function(_ingonal, idx){
            return React.DOM.td({key: idx},
              React.DOM.input({
                type: 'text',
                'data-idx': idx,
              })
            );
          })
        )
      },
      // 定义了当搜索文本框onchange后，进行数组的filter
      _search: function(ev) {
        // 获取事件源文本框的文本
        var needle = ev.target.value.toLowerCase();
        if(!needle) {
          this._logState({
            data: this._preSearchData,
          });
          return;
        }
        // 通过dataset，获取事件源是第几个td
        var evidx = ev.target.dataset.idx;
        var mubiaoarr = this._preSearchData.filter(function(row){
          return row[evidx].toString().toLowerCase().indexOf(needle) > -1;
        });

        this._logState({
          data: mubiaoarr,
        });
      },

      // 整个组件的渲染方法入口
      render: function(){
        return (
          React.DOM.div(null,
            this._renderToolbar(),
            this._renderTable()
          )
        );
      }



    });

    ReactDOM.render(
      // 用自定义组建创建
      // 直接用React.createElement(标签, 属性, 子元素),
      // React.createElement("span", null, "this is 直接创建 element"),
      React.createElement(excel, {
        header: header,
        initdata: exceldata,
      }),
      document.getElementById("app")
    )
  </script>
</body>
</html>
